<template>
  <div class="addListGoods_cont">
    <!-- 面包屑导航栏区域 -->
    <el-breadcrumb separator-class="el-icon-arrow-right">
      <el-breadcrumb-item :to="{ path: '/home' }">首页</el-breadcrumb-item>
      <el-breadcrumb-item>商品管理</el-breadcrumb-item>
      <el-breadcrumb-item>添加商品</el-breadcrumb-item>
    </el-breadcrumb>
    <!-- 卡片视图区域 -->
    <el-card>
      <el-alert title="添加商品信息" type="success" center show-icon :closable="false">
      </el-alert>
      <!-- 进度-步骤条区域 -->
      <el-steps :active="elStepsIndex" finish-status="success" align-center>
        <el-step title="基本信息"></el-step>
        <el-step title="商品参数"></el-step>
        <el-step title="商品属性"></el-step>
        <el-step title="商品图片"></el-step>
        <el-step title="商品内容"></el-step>
        <el-step title="完成"></el-step>
      </el-steps>
      <!-- 添加内容区域 -->
      <el-form :model="addListGoodsForm" :rules="addListGoodsFormRules" ref="addListGoodsFormRef" label-position="top">
        <el-tabs :tab-position="`left`" v-model="tabsActiveName" @tab-click="elTabs_Steps_But"
          :before-leave="goTabsUrl">
          <!-- 添加-基本信息表单区域 -->
          <el-tab-pane label="基本信息" name="0">
            <el-form-item label="商品名称" prop="goods_name">
              <el-input v-model="addListGoodsForm.goods_name"></el-input>
            </el-form-item>
            <el-form-item label="商品数量" prop="goods_number">
              <el-input v-model.number=" addListGoodsForm.goods_number">
              </el-input>
            </el-form-item>
            <el-form-item label="商品价格/元" prop="goods_price">
              <el-input v-model.number="addListGoodsForm.goods_price"></el-input>
            </el-form-item>
            <el-form-item label="商品重量/g" prop="goods_weight">
              <el-input v-model.number="addListGoodsForm.goods_weight"></el-input>
            </el-form-item>
            <el-form-item label="商品分类" prop="goods_cat">
              <!-- 联级选择器 -->
              <el-cascader expand-trigger="hover" :options="getCateListData" v-model="addListGoodsForm.goods_cat"
                @change="CateListHandleChange" :props="CateListProps" :clearable="true" class="add_cascader">
              </el-cascader>
            </el-form-item>
          </el-tab-pane>
          <!-- 添加商品动态参数区域 -->
          <el-tab-pane label="商品参数" name="1">
            <el-form-item v-for="(item,index1) in manyCategoriesList" :key="item.attr_id" :label="item.attr_name"
              :class="[`checkbox_form_item`]">
              <!-- 多选框 -->
              <el-checkbox-group v-model="manyCheckList">
                <el-checkbox v-for="(vals_txt,index2) in item.attr_vals" :label="vals_txt" border size="mini"
                  @change="checkboxManyChang(index1,index2,$event)" v-show="vals_txt.trim().length > 0"></el-checkbox>
              </el-checkbox-group>
            </el-form-item>
          </el-tab-pane>


          <!-- 添加商品静态属性区域 -->
          <el-tab-pane label="商品属性" name="2">
            <el-form-item :label="item.attr_name" v-for="item in onlyCategoriesList" :key="item.attr_id">
              <el-input v-model="item.attr_vals"></el-input>
            </el-form-item>
          </el-tab-pane>
          <!-- 添加商品图片区域 -->
          <el-tab-pane label="商品图片" name="3">
            <el-upload class="upload-demo" :action="imgUploadAction" :headers="imgUploadHeadersToken"
              :on-preview="imgHandlePreview" :on-remove="imgHandleRemove" list-type="picture"
              :on-success="imgOnSuccess">
              <el-button size="small" type="primary">点击上传</el-button>
              <div slot="tip" class="el-upload__tip">只能上传jpg/png文件，且不超过500kb</div>
            </el-upload>
          </el-tab-pane>
          <!-- 添加商品介绍区域 -->
          <el-tab-pane label="商品内容" name="4">
            <!-- 富文本编辑器 -->
            <quill-editor v-model="addListGoodsForm.goods_introduce" />
          </el-tab-pane>
          <!-- 确定添加商品按钮-区域 -->
          <el-tab-pane label="确定添加" name="5" class="tabAddButton">
            <el-alert title="确定添加商品" type="success" description="请确保您输入的商品信息内容完全正确后再点击【添加按钮】" show-icon
              :closable="false">
            </el-alert>
            <el-button type="primary" @click="addTabButtonOk"><i class="el-icon-circle-plus-outline"></i>确定添加
            </el-button>
          </el-tab-pane>
        </el-tabs>
      </el-form>
    </el-card>
    <!-- 图片上传-点击图片-预览图片的对话框 -->
    <el-dialog title="图片预览" :visible.sync="imgUploadDialogVisible" width="50%" :before-close="imgDialogHandleClose"
      :close-on-click-modal="false">
      <img :src="imgUploadDialog" alt="上传图片预览">
    </el-dialog>
  </div>
</template>

<script>
  export default {
    data() {
      // 自定义校验，用时必须额外放在一个对象里
      let num = (rule, value, callback) => {
        if (value > 0) { // 数字需要大于0
          return callback();
        } else {
          callback(new Error('必须是大于0的数字'));
        }
      }
      return {
        // 进度-步骤条的激活状态（0开始）
        elStepsIndex: 0,
        // 添加内容的标签页被选中的name
        tabsActiveName: '0',
        // 添加内容的表单数据
        addListGoodsForm: {
          goods_name: '',
          goods_number: '',
          goods_price: '',
          goods_weight: '',
          goods_cat: [],
          pics: [],
          goods_introduce: ''
        },
        // 添加内容的表单数据校验规则
        addListGoodsFormRules: {
          goods_name: [{
            required: true,
            message: '商品名称不能为空'
          }],
          goods_number: [{
            required: true,
            message: '商品数量不能为空'
          }, {
            type: 'number',
            message: '商品数量必须为数字值'
          }, {
            validator: num // 必需额外放进一个对象里
          }],
          goods_price: [{
            required: true,
            message: '商品价格不能为空'
          }, {
            type: 'number',
            message: '商品价格必须为数字值'
          }, {
            validator: num // 必需额外放进一个对象里
          }],
          goods_weight: [{
            required: true,
            message: '商品重量不能为空'
          }, {
            type: 'number',
            message: '商品重量必须为数字值'
          }, {
            validator: num // 必需额外放进一个对象里
          }],
          goods_cat: [{
            required: true,
            message: '商品分类不能为空'
          }]
        },
        // 商品列表
        getCateListData: [],
        // 商品列表联级选择器的props参数
        CateListProps: {
          value: 'cat_id',
          label: 'cat_name',
          children: 'children'
        },
        // 标签页的跳转与否
        tabBool: false,
        // 商品的商品动态参数列表
        manyCategoriesList: [],
        // 商品参数的多选框的选择数据
        manyCheckList: [],
        // 商品参数的多选框选中的对象
        manyCheckList_Array: [],
        // 商品的商品静态属性列表
        onlyCategoriesList: [],
        // 上传文件的请求路径
        imgUploadAction: 'http://127.0.0.1:8888/api/private/v1/upload',
        // 上传文件的请求需要携带的token请求头（main.js）
        imgUploadHeadersToken: {
          Authorization: window.sessionStorage.getItem('token')
        },
        // 图片上传的点击图片-预览图片对话框的显示与否
        imgUploadDialogVisible: false,
        // 图片上传的预览图片地址
        imgUploadDialog: ''
      }
    },
    created() {
      this.getCateList()
    },
    computed: {
      // 保存商品的分类id（三级）
      CateId() {
        if (this.addListGoodsForm.goods_cat.length === 3) {
          return this.addListGoodsForm.goods_cat[2]
        } else {
          return null
        }
      }
    },
    methods: {
      // 获取商品分类数据列表
      async getCateList() {
        let {
          data: res
        } = await this.$http.get('/categories')

        if (res.meta.status === 200) {
          this.getCateListData = res.data
        } else {
          this.$message.error("商品分类数据列表获取失败！")
        }
      },


      // 获取商品的参数列表数据
      async getCategoriesList(get_sel) {
        let {
          data: res
        } = await this.$http.get(`categories/${this.CateId}/attributes`, {
          params: {
            sel: get_sel
          }
        })

        if (res.meta.status === 200) {
          if (get_sel === "many") {
            this.manyCategoriesList = res.data
            // 将attr_vals转换成数组，供复选框展示
            this.manyCategoriesList.forEach((item1,index1) => {
              item1.attr_vals = item1.attr_vals.split(" ")
            })
          } else {
            this.onlyCategoriesList = res.data
            // 将静态属性的值设置为空，避免一开始有内容
            this.onlyCategoriesList.forEach(item => {
              item.attr_vals = ''
            })
          }
        } else {
          this.$message.error("商品参数获取失败")
        }
      },


      // 点击添加内容的标签页函数，控制进度-步骤条激活状态
      elTabs_Steps_But() {
        // elStepsIndex需要Number类型，而tabsActiveName是字符串，所以前面需要添加 + 号进行类型转换
        this.elStepsIndex = +this.tabsActiveName
      },

      // 基础信息-下拉联级选择器选择数据
      CateListHandleChange() {
        // 清空商品参数和商品属性
        this.manyCategoriesList = []
        this.onlyCategoriesList = []

        // 有些没有三级分类，但是请求会导致bug，所以必须是三级分类
        if (this.addListGoodsForm.goods_cat.length !== 3) {
          this.addListGoodsForm.goods_cat = []
          this.$message.success("请选择三级分类")
        } else {
            this.getCategoriesList("many")
            this.getCategoriesList("only")
        }
      },


      // 标签页切换跳转前的钩子函数，返回false且阻止切换
      goTabsUrl(activeName, oldActiveName) {
        // 标签页name：要跳转的-activeName，跳转前-oldActiveName

        /* ---------------------------- 重点 ------------------------------
          let：声明的变量，只在let命令所在的代码块内有效，不允许重复声明
          let：声明的变量一定要在声明后使用，否则报错，声明后可以改变
          var：变量可以在声明之前使用，值为undefined
          const：声明一个只读的常量。一旦声明，基本类型的常量值就不能改变，改变就报错
          const：只声明不赋值，就会报错。在声明所在的块级作用域内有效
          const：声明的复合类型的数据（主要是对象和数组）就可以改变，也可以增加
          ---------------------- 尽量都用let！！！ --------------------
        */

        /* 
          let bool = false 这里不能使用这个，使用一次后就失效了
          是局部变量赋值，只要走的：activeName不全等于'1'的话，就不会赋值为真了
          需要使用组件的data数据变量，昨天的有bug
        */

        if (activeName !== '0') { // 点击的不是第一个标签页
          this.$refs.addListGoodsFormRef.validate((valid, data) => {
            // valid是布尔值
            this.tabBool = valid
          })
        } else { // 点击的第一个标签页
          this.tabBool = true
        }
        return this.tabBool
      },


      // 文件上传成功时的钩子
      imgOnSuccess(response) {
        /* ---- 参数 ----
          response：服务器返回的数据；
          file：上传的文件的信息
          fileList：上传的文件列表
        */

        // 获取上传的文件的临时路径，拼接出一个图片信息的对象
        let pics_val = {
          pic: response.data.tmp_path
        }

        // 将图片的对象信息，添加进：addListGoodsForm.pics的数组内
        this.addListGoodsForm.pics.push(pics_val)
      },


      // 文件列表移除文件时的钩子
      imgHandleRemove(file) {
        /*
          获取要删除的文件的临时路径
          从addListGoodsForm.pics的数组中找到对应的索引值
          调用数组的：splice()方法
          从addListGoodsForm.pics的数组中移除这个要删除的文件的对象信息
        */

        let filePath = file.response.data.tmp_path

        /* ------------------------- 重点 -------------------------
          findIndex()：方法
          返回传入一个测试条件（函数）符合条件的数组第一个元素位置。
          为数组中的每个元素都调用一次函数执行；
          但是当数组中的元素在测试条件时返回 true 时，返回符合的元素的索引位置，之后的值不会再调用执行函数
          如果没有符合条件的元素返回 -1
        */
        let filePath_index = this.addListGoodsForm.pics.findIndex(val => val.pic === filePath)

        /* -------------------------- splice()方法 -----------------------------------
          splice(index,howmany,item1,itemX)：删除或者添加元素
          index：必需插入或（和）删除的数组元素的下标，必须是数字
          howmany：可选。删除多少元素。必须是数字，但可以是 "0"。若未规定，则删除从 index 开始到原数组结尾的所有元素
          item：可选。要添加到数组的新元素
          纯添加：  a=[1,2,3]； a.splice(1,0,"1","2")； a=[1, '1', '2', 2, 3]
          可添加删除并用
        */
        this.addListGoodsForm.pics.splice(filePath_index, 1)
      },


      // 点击文件列表中已上传的文件时的钩子
      imgHandlePreview(file) {
        this.imgUploadDialog = file.response.data.url
        this.imgUploadDialogVisible = true
      },

      // 商品图片-点击已上传文件弹出的预览框的关闭回调
      imgDialogHandleClose() {
        this.imgUploadDialogVisible = false
        this.imgUploadDialog = ''
      },


      // ------------------------------- 重点处理 -----------------------------
      // 多选框被点击时触发
      checkboxManyChang(index1, index2, value_bool) {
        // index1：所属父级的索引  index2：自身在父级属性数组的索引  value_bool：选中或者取消true/false

        let father = this.manyCategoriesList[index1] // 父级对象

        // 若为选中，直接在数组内添加对象
        if (value_bool) {
          let many = {
            attr_id: father.attr_id,
            attr_value: father.attr_vals[index2] // 父级对象内的attr_vals数组下索引为index2的值（点击获取）
          }
          this.manyCheckList_Array.push(many)

        } // 若为取消
        else {
          // 将选中时添加的数组进行循环，若传进来的值：attr_id等于父级的attr_id；attr_value等于被点击取消的父级attr_vals[index2]；则返回其在此数组中的索引。然后用splice()函数删除
          this.manyCheckList_Array.filter((val, index3) => {
            if (val.attr_id === father.attr_id && val.attr_value ===
              father.attr_vals[index2]) {
              this.manyCheckList_Array.splice(index3, 1)
            }
          })
        }
      },


      // 确定添加-点击确定添加商品的按钮-回调
      async addTabButtonOk() {
        // 处理静态属性的对象数组
        let only = []
        this.onlyCategoriesList.forEach(item => {
          if (item.attr_vals.trim().length > 0) {
            let only_Array = {
              attr_id: item.attr_id,
              attr_vals: item.attr_vals
            }
            only.push(only_Array)
          }
        })

        /* --------------------------- 重点 -------------------------
          深拷贝与浅拷贝：
          1、浅拷贝：vue中由于双向绑定机制，用等号直接赋值时，是传递的引用（内存地址索引），而不是直接传递的值，所以当被赋值的变量改变是会影响原始值，这种赋值方式就是所谓的浅拷贝
            1-1、（dataB = this.dataA;）
            1-2、（Object.assign()：https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign）

          2、深拷贝：不希望后续的变量操作影响原始值，所以这里就需要用到深拷贝。最简单的深拷贝方式是：
            2-1、（把转换成字符串，再转换成对象：dataB = JSON.parse(JSON.stringify(this.dataA))）
            2-2、（深度递归克隆：Lodash _.cloneDeep()）
            
            
        */
        let add = JSON.parse(JSON.stringify(this.addListGoodsForm))
        add.goods_cat = add.goods_cat.join(',')
        add.attrs = [...this.manyCheckList_Array, ...only]

        let {
          data: res
        } = await this.$http.post('goods', add)

        if (res.meta.status === 201) {
          // 跳转回商品列表组件
          this.$router.push('/goods')
          this.$message.success("此商品添加成功")
        } else {
          /*------------------------------- 重点----------------------------
            his.$data 获取当前状态下的data
            this.$options.data() 获取该组件初始状态下的data
            Object.assign() 方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。将返回目标对象。
            1、重置data中的初始值：
              Object.assign(this.$data, this.$options.data())：报错，暂时换方法解决（上面延时器）
            2、初始化 data 中的 footData 数据：
              Object.assign(this.$data.footData, this.$options.data().footData);
          */
          // ---------------------------- 定时器 （直接用无用）
          // clearTimeout(setTimeOut)
          // let setTimeOut = setTimeout(function () {
          // this.$router.go()
          // }, 2000)
          // ---------------------------- $nextTick()将回调延迟到下次 DOM 更新循环之后执行。 （无用）
          // this.$nextTick(() => {
          // //   // 刷新当前路由，目的初始化组件数据
          //   this.$router.go()
          // })

          /* ----------------------------------- 重点 ------------------------------------
          Promise(function (resolve, reject) ：
          三种状态：
            1、pending：等待状态，比如正在进行网络请求，或者定时器没有到时间。
            2、pfulfill：满足状态，当我们主动回调了resolve时，就处于该状态，并且会回调.then()
            3、preject：拒绝状态，当我们主动回调了reject时，就处于该状态，并且会回调.catch()

          resolve和reject。它们是两个函数，由 JavaScript 引擎提供
          执行成功会：调用resolve()，这个时候，我们后续的then会被回调。(可写传递的内容)
          执行失败：会调用reject()，这个时候，我们后续的catch会被回调。(可写传递的内容)
          */
          let _this = this
          let setTimeOut = null
          let promise = new Promise(function (resolve, reject) {
            _this.$message.error("此商品添加失败")
            clearTimeout(setTimeOut)
            resolve("成功");
          });
          promise.then(function (v) {
            setTimeOut = setTimeout(function () {
              _this.$router.go()
            }, 1500)
          });
        }

      },


    }
  }

</script>

<style lang="less" scoped>

</style>
